package edu.uoc.vmerinom.tfc.service;

import java.util.Date;
import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.Query;

import org.jboss.seam.ScopeType;
import org.jboss.seam.annotations.In;
import org.jboss.seam.annotations.Logger;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Scope;
import org.jboss.seam.annotations.Transactional;
import org.jboss.seam.log.Log;

import edu.uoc.vmerinom.tfc.domain.Usuario;

/**
 * Clase de servicios con las operaciones de acceso a base de datos para el
 * módulo de usuarios.
 * 
 * @author Víctor Manuel Merino Martínez
 * 
 */
@Name("usuariosService")
@Scope(ScopeType.CONVERSATION)
public class UsuariosService {
	/**
	 * Gestor de entidades.
	 */
	@In(create = true)
	EntityManager entityManager;

	/**
	 * Log para la trazabilidad de errores.
	 */
	@Logger
	Log log;

	/**
	 * Obtiene un usuario a partir del código de usuario y la contraseña. Si el
	 * usuario no se encuentra, el método devolverá <code>null</code>.
	 * 
	 * @param user
	 *            Código del usuario.
	 * @param pass
	 *            Contraseña del usuario.
	 * @return Usuario
	 */
	public Usuario obtenerUsuario(String user, String pass) {
		if (log.isDebugEnabled()) {
			log.debug("UsuariosService.obtenerUsuario(user:" + user + ", pass:" + pass + ")");
		}
		final String query = "from Usuario u left join fetch u.perfil where u.user like :user and u.pass like :pass and (u.baja is null or u.baja > :currentDate)";
		log.info(query);
		Usuario usuario = null;
		try {
			usuario = (Usuario) entityManager.createQuery(query).setParameter("user", user).setParameter("pass", pass).setParameter("currentDate", new Date())
					.getSingleResult();
		} catch (final NoResultException e) {
			usuario = null;
		}
		return usuario;
	}

	/**
	 * Obtiene un usuario a partir del código de usuario. Permite también
	 * filtrar por el identificador del usuario, para comprobar si existe algún
	 * usuario con el código de usuario indicado pero que no tenga el
	 * identificador indicado (sólo se filtrará por el identificador si el
	 * parámetro es distinto de <code>null</code>). Si el usuario no se
	 * encuentra, el método devolverá <code>null</code>.
	 * 
	 * @param user
	 *            Código del usuario.
	 * @param id
	 *            Identificador del usuario.
	 * @return Usuario
	 */
	public Usuario obtenerUsuario(String user, Integer id) {
		if (log.isDebugEnabled()) {
			log.debug("UsuariosService.obtenerUsuario(user:" + user + ", id:" + id + ")");
		}
		final StringBuffer queryString = new StringBuffer("from Usuario u left join fetch u.perfil where upper(u.user) like upper(:user)");
		if (id != null) {
			queryString.append(" and u.id != :id");
		}
		log.info(queryString);
		Query query = entityManager.createQuery(queryString.toString()).setParameter("user", user);
		if (id != null) {
			query.setParameter("id", id);
		}
		Usuario usuario = null;
		try {
			usuario = (Usuario) query.getSingleResult();
		} catch (final NoResultException e) {
			usuario = null;
		}
		return usuario;
	}

	/**
	 * Obtiene un usuario a partir del DNI. Permite también filtrar por el
	 * identificador del usuario, para comprobar si existe algún usuario con el
	 * DNI indicado pero que no tenga el identificador indicado (sólo se
	 * filtrará por el identificador si el parámetro es distinto de
	 * <code>null</code>). Si el usuario no se encuentra, el método devolverá
	 * <code>null</code>.
	 * 
	 * @param dni
	 *            DNI del usuario.
	 * @param id
	 *            Identificador del usuario.
	 * @return Usuario
	 */
	public Usuario obtenerUsuarioPorDNI(String dni, Integer id) {
		if (log.isDebugEnabled()) {
			log.debug("UsuariosService.obtenerUsuarioPorDNI(dni:" + dni + ", id:" + id + ")");
		}
		final StringBuffer queryString = new StringBuffer("from Usuario u left join fetch u.perfil where upper(u.dni) like upper(:dni)");
		if (id != null) {
			queryString.append(" and u.id != :id");
		}
		log.info(queryString);
		Query query = entityManager.createQuery(queryString.toString()).setParameter("dni", dni);
		if (id != null) {
			query.setParameter("id", id);
		}
		Usuario usuario = null;
		try {
			usuario = (Usuario) query.getSingleResult();
		} catch (final NoResultException e) {
			usuario = null;
		}
		return usuario;
	}

	/**
	 * Obtiene un usuario a partir del correo electrónico. Permite también
	 * filtrar por el identificador del usuario, para comprobar si existe algún
	 * usuario con el correo electrónico indicado, pero que no tenga el
	 * identificador indicado (sólo se filtrará por el identificador si el
	 * parámetro es distinto de <code>null</code>). Si el usuario no se
	 * encuentra, el método devolverá <code>null</code>.
	 * 
	 * @param email
	 *            Correo electrónico del usuario.
	 * @param id
	 *            Identificador del usuario.
	 * @return Usuario
	 */
	public Usuario obtenerUsuarioPorEmail(String email, Integer id) {
		if (log.isDebugEnabled()) {
			log.debug("UsuariosService.obtenerUsuarioPorEmail(email:" + email + ", id:" + id + ")");
		}
		final StringBuffer queryString = new StringBuffer("from Usuario u left join fetch u.perfil where lower(u.email) like lower(:email)");
		if (id != null) {
			queryString.append(" and u.id != :id");
		}
		
		log.info(queryString);
		Query query = entityManager.createQuery(queryString.toString()).setParameter("email", email);
		if (id != null) {
			query.setParameter("id", id);
		}
		Usuario usuario = null;
		try {
			usuario = (Usuario) query.getSingleResult();
		} catch (final NoResultException e) {
			if (log.isInfoEnabled()) {
				log.info("No se ha encontrado el usuario con email " + email + ".");
			}
			usuario = null;
		}
		return usuario;
	}

	/**
	 * Devuelve un listado con todos los usuarios de BD (salvo los
	 * administradores) ordenados por nombre, primer apellido y segundo apellido
	 * cuyo identificador no coincida con el que recibe por parámetros (sólo se
	 * filtra si es distinto de <code>null</code>).
	 * 
	 * @param id
	 *            Identificador del usuario
	 * 
	 * @return Listado de usuarios
	 */
	@SuppressWarnings("unchecked")
	public List<Usuario> obtenerUsuarios(Integer id) {
		if (log.isDebugEnabled()) {
			log.debug("UsuariosService.obtenerUsuarios()");
		}
		final StringBuffer queryString = new StringBuffer("from Usuario u where u.perfil.administrador = false");
		if (id != null) {
			queryString.append(" and u.id != :id");
		}
		queryString.append(" order by u.nombre, u.primerApellido, u.segundoApellido");
		Query query = entityManager.createQuery(queryString.toString());
		if (id != null) {
			query.setParameter("id", id);
		}
		return query.getResultList();
	}

	/**
	 * Devuelve un listado con todos los usuarios de BD ordenados por nombre,
	 * primer apellido y segundo apellido cuyo identificador no coincida con el
	 * que recibe por parámetros (sólo se filtra si el identificador es distinto
	 * de <code>null</code>).
	 * 
	 * @param id
	 *            Identificador del usuario
	 * 
	 * @return Listado de usuarios
	 */
	@SuppressWarnings("unchecked")
	public List<Usuario> obtenerUsuariosAdmin(Integer id) {
		if (log.isDebugEnabled()) {
			log.debug("UsuariosService.obtenerUsuariosAdmin()");
		}
		final StringBuffer queryString = new StringBuffer("from Usuario u where u.perfil.administrador = false");
		if (id != null) {
			queryString.append(" and u.id != :id");
		}
		queryString.append(" order by u.nombre, u.primerApellido, u.segundoApellido");
		log.info(queryString);
		Query query = entityManager.createQuery(queryString.toString());
		if (id != null) {
			query.setParameter("id", id);
		}
		return query.getResultList();
	}

	/**
	 * Actualiza el estado del usuario para poder visualizar los datos actuales
	 * de base de datos.
	 * 
	 * @param usuario
	 *            Usuario a actualizar
	 */
	public void actualizarEstadoUsuario(Usuario usuario) {
		if (log.isDebugEnabled()) {
			log.debug("UsuariosService.actualizarEstadoUsuario(usuario:" + usuario + ")");
		}
		if (usuario != null && usuario.getId() != null) {
			entityManager.refresh(usuario);
		}
	}

	/**
	 * Inserta o modifica los datos del usuario que recibe por parámetros.
	 * 
	 * @param usuario
	 *            Usuario a inserta/modificar
	 */
	@Transactional
	public void insertarModificarUsuario(Usuario usuario) {
		if (log.isDebugEnabled()) {
			log.debug("UsuariosService.insertarModificarUsuario(usuario:" + usuario + ")");
		}
		if (usuario != null) {
			final Date hoy = new Date(System.currentTimeMillis());
			if (usuario.getId() == null) {
				usuario.setCreado(hoy);
			}
			usuario.setModificado(hoy);
			entityManager.persist(usuario);
		}
		entityManager.flush();
	}
}
